#!/usr/bin/env python
# encoding: utf-8
#
# PyGadu - Pythonic Gadu-Gadu implementation
# Copyright (c) 2005,2006 Zygmunt Krynicki
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


import sys
import struct

import Packet
import HTTPServices
import Util

import socket
import threading
import time
import Queue
import select
import struct

import re

class ThreadWorker(threading.Thread):
    def __init__(self, name=None):
        threading.Thread.__init__(self, name=name, verbose=False)
        self.__abort = False
        self.__pause = False
    def pause(self):
        self.__pause = True
    def resume(self):
        self.__pause = False
    def do_cancel(self):
        raise SystemExit
    def cancel(self):
        self.__pause = False
        self.__abort = True
    def checkState(self):
        while self.__pause:
            time.sleep(0.1)
        if self.__abort:
            self.do_cancel()

class PacketMaster(ThreadWorker):
    def __init__(self):
        ThreadWorker.__init__(self, "PacketMaster")
        self.sock = None
        self.incoming = Queue.Queue(10)
        self.outgoing = Queue.Queue(10)
    def setSocket(self, sock):
        self.sock = sock
    def sendPacket(self, packet):
        self.outgoing.put(packet)
    def recvPacket(self):
        return self.incoming.get()
    def recvPacketNowait(self):
        return self.incoming.get_nowait()
    def __sendPacket(self, packet):
        #print u"Sending packet: %s" % packet
        sys.stderr.write(u">>> %04X (%3d bytes): %s\n" % (packet.TYPE, len(packet.bytes), Packet.bytesAsHex(packet.bytes)))
        sys.stderr.write(u"....................: %s\n" % Packet.bytesAsText(packet.bytes))
        header = struct.pack("<ii", packet.TYPE, len(packet.bytes))
        self.sock.send(header + packet.bytes)
    def __recvPacket(self):
        header = self.sock.recv(8)
        if len(header) == 0:
            return None
        if len(header) < 8:
            raise Packet.CorruptedPacketError(header)
        type, length = struct.unpack("<ii", header)
        if length == 0:
            payload = ""
        else:
            payload = self.sock.recv(length)
            if len(payload) != length:
                raise Packet.CorruptedPacketError(header+payload)
        sys.stderr.write(u"<<< %04X (%3d bytes): %s\n" % (type, length, Packet.bytesAsHex(payload)))
        sys.stderr.write(u"....................: %s\n" % Packet.bytesAsText(payload))
        try:
            packet = Packet.incoming_packets[type](payload)
#            sys.stderr.write(u"Received packet: %s\n" % packet)
            return packet
        except KeyError:
            raise Packet.UnknownPacketError(header, payload)
        except struct.error:
            raise Packet.UnknownPacketError(header, payload)
    def run(self):
        while True:
            self.checkState()
            fd = self.sock.fileno()
            # print "Sleeping on select..."
            if self.incoming.full():
                rlist = []
            else:
                rlist = [fd]
            if self.outgoing.empty():
                wlist = []
            else:
                wlist = [fd]
            (rlist, wlist, xlist) = select.select(rlist, wlist, [], 1)
            # print "Selected, rlist:", rlist, " wlist:", wlist
            if rlist:
                try:
                    self.incoming.put(self.__recvPacket())
                except Queue.Full:
                    pass
                except Packet.UnknownPacketError, e:
                    print e
            if wlist:
                try:
                    self.__sendPacket(self.outgoing.get_nowait())
                except Queue.Empty:
                    pass

class SessionWatcher(ThreadWorker):
    TIMEOUT_SECONDS = 5*60 - 5
    def __init__(self, session):
        ThreadWorker.__init__(self, "SessionWatcher")
        self.session = session
    def run(self):
        while True:
            for i in xrange(self.TIMEOUT_SECONDS*10):
                self.checkState()
                time.sleep(0.1)
            self.session.sendPing()
           
class RawSession:
    def __init__(self, uin, password):
        self.uin = uin
        self.password = password
        self.sock = None
        self.seed = None
        self.packet_master = PacketMaster()
        self.watcher = SessionWatcher(self)
        self.watcher.pause()
        self.packet_master.pause()
        self.watcher.start()
        self.packet_master.start()
    def __connect(self):
        sdp = HTTPServices.ServerDiscoveryProtocol(self.uin, last_message=0)
        response = sdp.talk()
        if response:
            sys_message, server1, server2, message = response
            if server1:
                self.__connectTo(server1)
                return True
        return False
    def __connectTo(self, addr):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
        self.sock.setblocking(True)
        self.sock.connect(addr)
        self.packet_master.setSocket(self.sock)
        self.packet_master.resume()
        self.watcher.resume()
    def __disconnect(self):
        self.watcher.pause()
        self.packet_master.pause()
        self.sock.close()
    def __quit(self):
        self.disconnect()
        self.packet_master.cancel()
        self.watcher.cancel()
        self.onQuit()
    def remoteDisconnected(self):
        self.__disconnect()
    def disconnect(self):
        self.onDisconnect()
        self.__disconnect()
    def quit(self):
        self.__quit()
    def connect(self):
        self.onConnectionInitiated()
        if self.__connect():
            self.onConnectionSuccessful()
        else:
            self.onConnectionFailed()
    def onConnectionInitiated(self):
        pass
    def onConnectionSuccessful(self):
        pass
    def onConnectionFailed(self):
        pass
    def onDisconnect(self):
        pass
    def onQuit(self):
        pass
    def sendPacket(self, packet):
        self.packet_master.sendPacket(packet)
    def recvPacket(self):
        return self.packet_master.recvPacket()
    def recvPacketNowait(self):
        return self.packet_master.recvPacketNowait()
    def sendPing(self):
        self.sendPacket(Packet.Ping())
    def sendLogin(self, seed, version="6.0"):
        packet = Packet.Login60(
                Packet.Login60.VERSION[version],
                self.uin,
                Util.passwordHash(self.password, seed),
                Packet.NewStatus.BUSY_D, "pygadu")
        self.sendPacket(packet)
    def changeStatus(self, status, desc=None):
        self.sendPacket(Packet.NewStatus(status, desc))
    def sendMessage(self, uin, message, seq = 1, clazz = 0):
        message = message.rstrip()
        max = 1990
        while len(message) > max:
            self.sendPacket(Packet.OutgoingMessage(uin, seq, clazz, message[:max]))
            message = message[max:]
        self.sendPacket(Packet.OutgoingMessage(uin, seq, clazz, message))
    def sendReply(self, message, packet):
        self.sendMessage(packet.sender, message, packet.seq+1)
    def sendBuddyList(self, buddy_list):
        max_in_one_shot = 400
        while len(buddy_list) > max_in_one_shot:
            self.sendPacket(Packet.NotifyFirst(buddy_list[:max_in_one_shot]))
            buddy_list = buddy_list[max_in_one_shot:]
        self.sendPacket(Packet.NotifyLast(buddy_list))

class Buddy:
    TYPE_BUDDY   = 0x01
    TYPE_FRIEND  = 0x02
    TYPE_BLOCKED = 0x04
    def __init__(self, name, uin, type = TYPE_BUDDY):
        self.name = name
        self.uin = uin
        self.type = type
    def __str__(self):
        print "Buddy: %s/%d (%d)" % (self.name, self.uin, self.type)

class BuddyList:
    def __init__(self):
        self.list = []
    def add(self, buddy):
        self.list.append(buddy)
    def lookupName(self, uin):
        for buddy in self.list:
            if buddy.uin == uin:
                return buddy.name
        return None

class SimpleSession(RawSession, ThreadWorker):
    def __init__(self, uin, password):
        RawSession.__init__(self, uin, password)
        ThreadWorker.__init__(self, "NiceSession")
        self.start()
    def onWelcome(self, seed):
        self.sendLogin(seed)
    def onLoginOk(self):
        self.sendBuddyList(self.buddy_list.list)
        self.changeStatus(Packet.NewStatus.AVAIL_D, "pygadu")
    def onQuit(self):
        self.cancel()
    def onBuddyStatusInitial(self, uin, status, desc):
        pass
    def onBuddyStatusChanged(self, uin, status, desc):
        pass
    def onMessageReceived(self, uin, message, seq):
        pass
    def onMesssaeAckReceived(self, uin, status, seq):
        pass
    def onDisconnected(self):
        pass
    def onLoginFailed(self):
        pass
    def onPacketError(self, error):
        print "Packet error: ", error
    def run(self):
        try:
            while True:
                while 1:
                    try:
                        self.checkState()
                        packet = self.recvPacketNowait()
                        break
                    except Queue.Empty:
                        time.sleep(0.1)
                if not packet:
                    self.remoteDisconnected()
                elif isinstance(packet, Packet.Welcome):
                    self.onWelcome(packet.seed)
                elif isinstance(packet, Packet.Status50):
                    self.onBuddyStatusChanged(packet.uin, packet.status, packet.unicode_desc)
                elif isinstance(packet, Packet.NotifyReply60):
                    for info in packet.notify_list:
                        self.onBuddyStatusInitial(info.uin, info.status, info.unicode_desc)
                elif isinstance(packet, Packet.Status60):
                    self.onBuddyStatusChanged(packet.uin, packet.status, packet.unicode_desc)
                elif isinstance(packet, Packet.LoginOk):
                    self.onLoginOk()
                elif isinstance(packet, Packet.IncomingMessage):
                    self.onMessageReceived(packet.sender, packet.unicode_message, packet.seq)
                elif isinstance(packet, Packet.LoginFailed):
                    self.onLoginFailed()
                elif isinstance(packet, Packet.Disconnected):
                    self.onDisconnected()
        except Packet.PacketError, e:
            self.onPacketError(e)

class SimpleTextSession(SimpleSession):
    def __init__(self, uin, password):
        SimpleSession.__init__(self, uin, password)
        self.buddy_list = BuddyList()
    def main_loop(self):
        try:
            while 1:
                self.checkState()
                time.sleep(1)
        except KeyboardInterrupt:
            self.cancel()
    def onConnectionInitiated(self):
        print "Connecting..."
    def onLoginFailed(self):
        print "Login failed"
        self.quit()
    def onConnectionSuccessful(self):
        print "Connected"
    def onConnectionFailed(self):
        print "Connection failed"
    def onDisconnected(self):
        print "Disconnected"
    def onMessageReceived(self, uin, message, seq):
        buddy = self.buddy_list.lookupName(uin) or "not-on-list"
        print "<%s/%d> %s" % (buddy, uin, message)
    def onBuddyStatusInitial(self, uin, status, desc):
        buddy = self.buddy_list.lookupName(uin) or "not-on-list"
        STATUS_TEXT = {
            Packet.Status.BLOCKED:      "blocked",
            Packet.Status.AVAIL:        "available",
            Packet.Status.AVAIL_D:      "available",
            Packet.Status.BUSY:         "away",
            Packet.Status.BUSY_D:       "away",
            Packet.Status.NOT_AVAIL:    "offline",
            Packet.Status.NOT_AVAIL_D:  "offline",
            Packet.Status.INVISIBLE:    "invisible",
            Packet.Status.INVISIBLE_D:  "invisible"
        }
        if desc != "":
            print "<%s/%d> is %s: %s" % (buddy, uin, STATUS_TEXT[status], desc)
        else:
            print "<%s/%d> is %s" % (buddy, uin, STATUS_TEXT[status])
    def onBuddyStatusChanged(self, uin, status, desc):
        buddy = self.buddy_list.lookupName(uin) or "not-on-list"
        STATUS_TEXT = {
            Packet.Status.BLOCKED:      "blocked",
            Packet.Status.AVAIL:        "available",
            Packet.Status.AVAIL_D:      "available",
            Packet.Status.BUSY:         "away",
            Packet.Status.BUSY_D:       "away",
            Packet.Status.NOT_AVAIL:    "offline",
            Packet.Status.NOT_AVAIL_D:  "offline",
            Packet.Status.INVISIBLE:    "invisible",
            Packet.Status.INVISIBLE_D:  "invisible"
        }
        if desc != "":
            print "<%s/%d> is now %s: %s" % (buddy, uin, STATUS_TEXT[status], desc)
        else:
            print "<%s/%d> is now %s" % (buddy, uin, STATUS_TEXT[status])

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print "Usage: %s <uin> <password>" % (sys.argv[0])
        sys.exit()
    else:
        print """
        PyGadu version 01, Copyright (C) 2005,2006 Zygmunt Krynicki
        PyGadu comes with ABSOLUTELY NO WARRANTY; This is free software,
        and you are welcome to redistribute it under certain conditions;
        for details please read the file COPYING that has to be
        distributed with this software. 
        """
    uin = int(sys.argv[1])
    password = sys.argv[2]
    s = SimpleTextSession(uin, password)
    s.buddy_list.list = []
    s.connect()
    s.main_loop()
    time.sleep(0.1)
    s.quit()
    
